/**
 * Copyright (c) 2011 Nokia Corporation.
 *
 * CAUTION: This class is in early, experimental state.
 * NOTE, !!!! SOURCE ORIGINA HAS BEEN REMOVED FROM THIS VERSION OF THE SPRITEBATCH!!!
 *
 */


#include <QMatrix4x4>
#include <QDebug>
#include <math.h>
#include <memory.h>
#include "QtOpenGLSpriteBatch.h"



/*
   Shaders for the spritebatch rendering with a color multiply
*/
const char* strSpriteBatchFragmentShaderColorMultiplty =
    "uniform sampler2D sampler2d;\n"
    "varying lowp vec4 vertexColor;\n"
    "varying lowp vec2 texCoord;\n"
    "void main (void)\n"
    "{\n"
    "    gl_FragColor = texture2D(sampler2d, texCoord)*vertexColor;\n"
    "}";

const char* strSpriteBatchVertexShaderColorMultiply =
    "attribute highp vec3 vertex;\n"
    "uniform mediump mat4 projMatrix;\n"
    "uniform mediump mat4 im[16];\n"         // input
    "varying mediump vec2 texCoord;\n"
    "varying mediump vec4 vertexColor;\n"

    "void main(void)\n"
    "{\n"
        "mediump mat4 thisim = im[ int(vertex.z) ];\n"
        //"highp vec2 transVertex = vec2( thisim[2][0], thisim[2][1] ) + mat2( thisim[0][0], thisim[0][1], thisim[1][0], thisim[1][1]) * (vertex.xy-vec2(thisim[2][2]-0.5, thisim[2][3]-0.5));\n"
        "highp vec2 transVertex = vec2( thisim[2][0], thisim[2][1] ) + mat2( thisim[0][0], thisim[0][1], thisim[1][0], thisim[1][1]) * (vertex.xy);\n"
        "gl_Position = vec4(transVertex,1,1) * projMatrix;\n"
        "vertexColor = vec4( thisim[3][0], thisim[3][1], thisim[3][2], thisim[3][3] );\n"
        //"texCoord = (vec2(0.5+vertex.x, 0.5-vertex.y)) * vec2(thisim[1][2], thisim[1][3]) + vec2(thisim[0][2], thisim[0][3]);\n"
        "mediump vec2 suv  = vec2(thisim[2][2], thisim[2][3])*vertex.x + vec2(-thisim[2][3], thisim[2][2])*vertex.y;\n"
        "texCoord = (vec2(0.5+suv.x, 0.5-suv.y)) * vec2(thisim[1][2], thisim[1][3]) + vec2(thisim[0][2], thisim[0][3]);\n"
    "}";


/*
   Shaders for the spritebatch rendering with pure texture
*/
const char* strSpriteBatchFragmentShaderPureTexture =
    "uniform sampler2D sampler2d;\n"
    "varying mediump vec2 texCoord;\n"
    "void main (void)\n"
    "{\n"
    "    gl_FragColor = texture2D(sampler2d, texCoord);\n"
    "}";

const char* strSpriteBatchVertexShaderPureTexture =
    "attribute highp vec3 vertex;\n"
    "uniform mediump mat4 projMatrix;\n"
    "uniform mediump mat4 im[16];\n"         // input
    "varying mediump vec2 texCoord;\n"
    "void main(void)\n"
    "{\n"
        "mediump mat4 thisim = im[ int(vertex.z) ];\n"
        //"highp vec2 transVertex = vec2( thisim[2][0], thisim[2][1] ) + mat2( thisim[0][0], thisim[0][1], thisim[1][0], thisim[1][1]) * (vertex.xy-vec2(thisim[2][2]-0.5, thisim[2][3]-0.5));\n"
        "mediump vec2 transVertex = vec2( thisim[2][0], thisim[2][1] ) + mat2( thisim[0][0], thisim[0][1], thisim[1][0], thisim[1][1]) * (vertex.xy);\n"
        "gl_Position = vec4(transVertex,1,1) * projMatrix;\n"
        //"texCoord = (vec2(0.5+vertex.x, 0.5-vertex.y)) * vec2(thisim[1][2], thisim[1][3]) + vec2(thisim[0][2], thisim[0][3]);\n"

        "mediump vec2 suv  = vec2(thisim[2][2], thisim[2][3])*vertex.x + vec2(-thisim[2][3], thisim[2][2])*vertex.y;\n"
        "texCoord = (vec2(0.5+suv.x, 0.5-suv.y)) * vec2(thisim[1][2], thisim[1][3]) + vec2(thisim[0][2], thisim[0][3]);\n"

    "}";





QtOpenGLSpriteBatch::QtOpenGLSpriteBatch(int w, int h, QObject *parent ) : QObject(parent), SpriteBatch(w,h)
{
    for (int f=0; f<COSINE_TABLE_SIZE; f++)
        cosineTable[f] = cosf( (float)f / (float)COSINE_TABLE_SIZE * 3.14159f * 2.0f );


    batchCounter = 0;

    for (int f=0; f<2; f++) {
        QGLShader *vshader = new QGLShader(QGLShader::Vertex, this);
        const char *src = strSpriteBatchVertexShaderColorMultiply;
        if (f!=0) src = strSpriteBatchVertexShaderPureTexture;
        if (vshader->compileSourceCode( src ) == false)
            qDebug() << "QOGLSBatch: Failed to compile fragment shader" << f;
        else
            qDebug() << "compiled vs" << f;

        QGLShader *fshader = new QGLShader(QGLShader::Fragment, this);
        src = strSpriteBatchFragmentShaderColorMultiplty;
        if (f!=0) src = strSpriteBatchFragmentShaderPureTexture;
        if (fshader->compileSourceCode( src ) == false)
            qDebug() << "QOGLSBatch: Failed to compile fragment shader" << f;
        else
            qDebug() << "compiled" << f;

        program[f].addShader( vshader );
        program[f].addShader( fshader );
        program[f].bindAttributeLocation( "vertex", 0 );

        if (program[f].link() == false)
            qDebug() << "Error linking the program:"<<f;
        else
            qDebug() << "linked program:"<<f;


        samplerLocation[f] = program[f].uniformLocation("sampler2d");
        inputMatrixLocation[f] = program[f].uniformLocation("im");
        projmLocation[f] = program[f].uniformLocation("projMatrix");
    }



    qtvbo = new QGLBuffer( QGLBuffer::VertexBuffer );
    if (qtvbo->create() == false)
        qDebug() << "Failed to create vertexbuffer.";
    else
        qDebug() << "Created the vertexbufferobject";

    qtvbo->setUsagePattern( QGLBuffer::StaticDraw );



    GLfloat vertices[] = {-0.5f,-0.5f,0.0f, 0.5f,-0.5f,0.0f, 0.5f,0.5f,0.0f,    // triangle 1
                          -0.5f,-0.5f,0.0f, 0.5f,0.5f,0.0f, -0.5f,0.5f,0.0f};   // triangle 2

    GLfloat *tempVertices = new GLfloat[ 3 * 6 * BATCH_SIZE];
    for (int f=0; f<BATCH_SIZE; f++) {
        memcpy( tempVertices+f*3*6, vertices, 3*6*sizeof(GLfloat ));
        for (int g=0; g<6; g++) tempVertices[ f*3*6+2+g*3 ] = f;            // mark the index for each triangle
    }

    qtvbo->bind();
    qtvbo->allocate( tempVertices, BATCH_SIZE*6*sizeof(GLfloat)*3 );


    //glBindBuffer(GL_ARRAY_BUFFER, vbo );
    //glBufferData(GL_ARRAY_BUFFER, BATCH_SIZE *6*sizeof(GLfloat)*3, tempVertices, GL_STATIC_DRAW );

    delete [] tempVertices;
}


QtOpenGLSpriteBatch::~QtOpenGLSpriteBatch()
{
    if (qtvbo) delete qtvbo;
    qtvbo = 0;
}


void QtOpenGLSpriteBatch::flushSprites(bool useTable)
{
    if (batchCounter<1) return;         // nothing to fill
    float angletointmul = (float)COSINE_TABLE_SIZE/3.14159f/2.0f;

    int selectedProgram = 0;
    if (allWhite==true) selectedProgram = 1;
    //if (selectedProgram==1) qDebug() << "flushing with whiteprogram";
    if (currentProgram!=selectedProgram) setActiveProgram( selectedProgram );


        // Fill the inputTempMatrices
    //float *m = inputMatrixTemp;
    SpriteDrawInfo *sdi = batchCollection;
    for (int f=0; f<batchCounter; f++)
    {
        qreal *m = inputMatrixtemp[f].data();

            // targetposition
        m[8+0] = sdi->posx;
        m[8+1] = sdi->posy;


                // source angle
        if (useTable) {
            int siangle = ((int)( sdi->sourceAngle * angletointmul ) & COSINE_TABLE_AND);
            m[8+2] = cosineTable[ siangle ];
            m[8+3] = cosineTable[(siangle+COSINE_TABLE_SIZE/4)&COSINE_TABLE_AND];
        } else {
            m[8+2] = cosf( sdi->sourceAngle );
            m[8+3] = sinf( sdi->sourceAngle );
        }



            // Destination orientation
        if (sdi->manualTransformActive)
        {
            m[0] = sdi->manualTransformMatrix[0][0];
            m[1] = sdi->manualTransformMatrix[0][1];
            m[4] = sdi->manualTransformMatrix[1][0];
            m[5] = sdi->manualTransformMatrix[1][1];
        }
        else
        {
            if (useTable) {
                int iangle = ((int)( sdi->angle * angletointmul ) & COSINE_TABLE_AND);
                m[0] = cosineTable[iangle] * sdi->scaleX;
                m[1] = cosineTable[(iangle+COSINE_TABLE_SIZE/4)&COSINE_TABLE_AND] * sdi->scaleX;
                m[4+0] = cosineTable[(iangle+COSINE_TABLE_SIZE/4)&COSINE_TABLE_AND] * sdi->scaleY;
                m[4+1] = -cosineTable[iangle] * sdi->scaleY;
            } else {
                m[0] = cosf( sdi->angle ) * sdi->scaleX;
                m[1] = sinf( sdi->angle ) * sdi->scaleX;
                m[4+0] = sinf( sdi->angle ) * sdi->scaleY;
                m[4+1] = -cosf( sdi->angle ) * sdi->scaleY;
            }
        }


            // Source rectangle
        m[2] = sdi->sourcex;
        m[3] = sdi->sourcey;
        m[4+2] = sdi->sourceWidth;
        m[4+3] = sdi->sourceHeight;

        m[12+0] = sdi->r;
        m[12+1] = sdi->g;
        m[12+2] = sdi->b;
        m[12+3] = sdi->a;

        sdi++;
    }

    program[currentProgram].setUniformValueArray(inputMatrixLocation[currentProgram], inputMatrixtemp, batchCounter );
    glDrawArrays(GL_TRIANGLES, 0, 6*batchCounter);

    // Reset batch
    batchCounter = 0;
    allWhite = true;
}


#define WHITE_LIMIT 0.999f


void QtOpenGLSpriteBatch::draw ( SpriteDrawInfo *sdi, int spriteCount )
{
    SpriteDrawInfo *i = sdi;
    while (spriteCount>0) {

        bool completelyWhite = false;
        if ( i->r>WHITE_LIMIT && i->g>WHITE_LIMIT && i->b>WHITE_LIMIT && i->a > WHITE_LIMIT ) completelyWhite = true;

        if (allWhite==true && completelyWhite==false && batchCounter>=2) {
            // current would break the "allWhite"-flag. Flush this batch before adding it.
            //qDebug() << "Using the white program..";
            flushSprites();
        }

        if (i->textureHandle != currentTexture ) {
                // Texture have been changed, flush currently collected renderingdata
            flushSprites();
                // Change our current texture and activate it.
            currentTexture = i->textureHandle;
            glBindTexture( GL_TEXTURE_2D, currentTexture );
        }


        if (!completelyWhite) allWhite = false;
        memcpy( batchCollection + batchCounter, i, sizeof( SpriteDrawInfo ) );
        batchCounter++;
        if (batchCounter>=BATCH_SIZE) flushSprites();
        i++;
        spriteCount--;
    }
}


void QtOpenGLSpriteBatch::setActiveProgram( int programIndex )
{
    program[programIndex].bind();
    program[programIndex].setUniformValue(samplerLocation[programIndex], 0);
    program[programIndex].setUniformValue( projmLocation[programIndex], projectionMatrix );

    program[programIndex].enableAttributeArray(0);
    program[programIndex].setAttributeBuffer(0, GL_FLOAT, 0, 3, 0 );

    currentProgram = programIndex;
}



void QtOpenGLSpriteBatch::begin( BlendMode bmode, TransformMode dTransform, float *customProjectionMatrix)
{
    currentDestinationTransform = dTransform;


    if (customProjectionMatrix) {
        // set projection
        QMatrix4x4 projTemp( (qreal*)customProjectionMatrix );
        // NOTE, DEBUG, ERROR, WARNING, TODO ..... THIS might have to be transposed. Test when using !!!!!! Otherwise you won't see anything.
        projectionMatrix = projTemp;
        //program.setUniformValue( projmLocation, projTemp );
    } else {

        // NOTE, .. HACK FOR HARMATTAN
#ifdef Q_WS_MAEMO_6_removed_as_firecup_is_landscape_app
        qreal mproj[16] = {
            0, 2.0f/(float)targetWidth,  0, 0,
            2.0f/(float)targetHeight, 0, 0, 0,          // final height
            0, 0, 1, 0,
            -1.0f,-1.0f, 0, 1 };

#else
            // WITH Other GL - implementations, the matrix is otherway around.
            // NOTE, SERIOUS ERROR FOUND IN QTOPENGL. different matrix orientations with setUniformValue / and setUniformValueArray (QMatrix4x4).
        qreal mproj[16] = {
            2.0f/(float)targetWidth, 0, 0, 0,
            0, -2.0f/(float)targetHeight, 0, 0,
            0, 0, 1, 0,
            -1.0f,1.0f, 0, 1,
        };

#endif

        projectionMatrix = QMatrix4x4( mproj );
        //QMatrix4x4 projTemp( mproj );
        //program.setUniformValue( projmLocation, projTemp );
    }


    glDisable( GL_DEPTH_TEST );
    glDepthMask( GL_FALSE );
    glEnable( GL_BLEND );
    if (bmode==eALPHA)
        glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    else
        glBlendFunc( GL_SRC_ALPHA, GL_ONE);

    glDisable( GL_CULL_FACE );

    qtvbo->bind();


    currentTexture = 0;
    allWhite = true;
    currentProgram = -1;
}


void QtOpenGLSpriteBatch::end()
{
        // Draw everything that is collected.
    flushSprites();
}
